package org.changs.servlet;

import android.support.annotation.NonNull;

import com.google.gson.JsonObject;

import org.changs.aplug.utils.FileUtils;
import org.changs.aplug.utils.JsonUtils;
import org.changs.servlet.socket.CoolSocket;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;

import io.reactivex.Observable;
import timber.log.Timber;

/**
 * Created by yincs on 2017/8/28.
 */

public class AplugClient {

    public static Observable<JsonObject> rxSend(String address, int socketPort, @NonNull JsonObject message) {
        final SenderRunnable senderRunnable = new SenderRunnable(new InetSocketAddress(address, socketPort),
                message.toString());
        return Observable.fromCallable(senderRunnable::request);
    }

    public static JsonObject send(String address, int socketPort, String message) throws Exception {
        return send(new InetSocketAddress(address, socketPort), message);
    }

    public static JsonObject send(InetSocketAddress address, String message) throws Exception {
        return new SenderRunnable(address, message).request();
    }

    public static void send(String address, int socketPort, JsonObject message, File file, SenderFileCallback callback) {
        final SenderFileRunnable senderRunnable = new SenderFileRunnable(new InetSocketAddress(address, socketPort), message, file, callback);
        senderRunnable.run();
    }

    public static void send(InetSocketAddress address, JsonObject message, File file, SenderFileCallback callback) {
        final SenderFileRunnable senderRunnable = new SenderFileRunnable(address, message, file, callback);
        senderRunnable.run();
    }


    public static void receive(String address, int socketPort, JsonObject message, ReceiveFile receiveFile, ReceiveFileCallback callback) {
        final ReceiveRunnable receiveRunnable = new ReceiveRunnable(new InetSocketAddress(address, socketPort), message, receiveFile, callback);
        receiveRunnable.run();
    }

    public static void receive(InetSocketAddress address, JsonObject message, ReceiveFile receiveFile, ReceiveFileCallback callback) {
        final ReceiveRunnable receiveRunnable = new ReceiveRunnable(address, message, receiveFile, callback);
        receiveRunnable.run();
    }


    private static class SenderRunnable {

        private final SocketAddress mAddress;
        private final String message;


        public SenderRunnable(SocketAddress mAddress, String message) {
            this.mAddress = mAddress;
            this.message = message;
        }


        public JsonObject request() throws Exception {
            Socket socket = new Socket();

            socket.bind(null);
            socket.connect(mAddress);

//                if (this.mProcess.getSocketTimeout() != NO_TIMEOUT)
            socket.setSoTimeout(2000);

            PrintWriter writer = IOUtils.getStreamWriter(socket.getOutputStream());

            if (message != null) writer.append(message);

            writer.append(CoolSocket.END_SEQUENCE);
            writer.flush();

            final String response = IOUtils.readStreamMessage(socket.getInputStream(), IOUtils.NO_TIMEOUT);
            System.out.println("response = " + response);
            return JsonUtils.parseObject(response, JsonObject.class);
        }
    }

    private static class SenderFileRunnable implements Runnable {
        private final SocketAddress address;
        private final JsonObject message;
        private final File file;
        private final SenderFileCallback senderFileCallback;
        private byte[] buffer = CommunicationConfig.DEFAULT_BUFFER_SIZE;
        private int notifyDelay = 300;
        private boolean isInterrupted = false;

        private SenderFileRunnable(SocketAddress address, JsonObject message, File file, SenderFileCallback senderFileCallback) {
            this.address = address;
            this.message = message;
            this.file = file;
            this.senderFileCallback = senderFileCallback;
        }

        @Override
        public void run() {
            boolean fileLegal = file.exists() && file.isFile();
            if (!fileLegal) {
                senderFileCallback.onError(0, "文件不存在");
                return;
            }

            Socket socket = new Socket();

            try {
                socket.bind(null);
                socket.connect(address);
                senderFileCallback.onNext(0, "已连接上");

                PrintWriter writer = IOUtils.getStreamWriter(socket.getOutputStream());
                writer.write(IOUtils.SEND_FILE_TYPE);
                writer.append(CoolSocket.END_SEQUENCE);
                writer.flush();

                final String response = IOUtils.readStreamMessage(socket.getInputStream(), IOUtils.NO_TIMEOUT);
                System.out.println("response = " + response);
                if (!response.equals(IOUtils.OK)) {
                    //不可以传
                    senderFileCallback.onError(0, "状态错误");
                    return;
                }

                message.addProperty(IOUtils.EXTRA_FILE_NAME, file.getName());
                message.addProperty(IOUtils.EXTRA_FILE_SIZE, file.length());
                writer.append(message.toString());
                writer.append(CoolSocket.END_SEQUENCE);
                writer.flush();


                final String response2 = IOUtils.readStreamMessage(socket.getInputStream(), IOUtils.NO_TIMEOUT);
                Timber.d("response2 = " + response2);
                if (!response2.equals(IOUtils.OK)) {
                    //服务器拒绝接收
                    senderFileCallback.onError(0, response2);
                    return;
                }
                senderFileCallback.onNext(0, "正在传输文件");
                //开始发送文件
                FileInputStream inputStream = new FileInputStream(file);
                OutputStream outputStream = socket.getOutputStream();

                int len;
                int progressPercent = -1;
                long lastNotified = System.currentTimeMillis();

                while ((len = inputStream.read(buffer)) > 0) {
                    outputStream.write(buffer, 0, len);
                    outputStream.flush();

                    if ((System.currentTimeMillis() - lastNotified) > notifyDelay) {
                        int currentPercent = (int) (((float) 100 / file.length()) * inputStream.getChannel().position());

                        if (currentPercent > progressPercent) {
                            senderFileCallback.onProgress(currentPercent);
                            progressPercent = currentPercent;
                        }

                        lastNotified = System.currentTimeMillis();
                    }

                    if (isInterrupted)
                        break;
                }
                if (!isInterrupted) {
                    senderFileCallback.onSuccess();
                }
                outputStream.close();
                inputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
                senderFileCallback.onError(0, "传输失败");
            } finally {
                try {
                    socket.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                senderFileCallback.onComplete();
            }
        }
    }

    private static class ReceiveRunnable implements Runnable {
        private final SocketAddress address;
        private final JsonObject message;
        private final ReceiveFile receiveFile;
        //        private final File file;
        private final ReceiveFileCallback receiveFileCallback;
        private byte[] buffer = CommunicationConfig.DEFAULT_BUFFER_SIZE;
        private int notifyDelay = 300;
        private boolean isInterrupted = false;
//        private final long fileSize = 2000;


        private ReceiveRunnable(SocketAddress address, JsonObject message, ReceiveFile receiveFile, ReceiveFileCallback receiveFileCallback) {
            this.address = address;
            this.message = message;
            this.receiveFile = receiveFile;
            this.receiveFileCallback = receiveFileCallback;
        }

        @Override
        public void run() {
            Socket socket = new Socket();

            try {
                socket.bind(null);
                socket.connect(address);
                PrintWriter writer = IOUtils.getStreamWriter(socket.getOutputStream());

                //告诉服务器此次socket的功能
                writer.write(IOUtils.RECEIVE_FILE_TYPE);
                writer.append(CoolSocket.END_SEQUENCE);
                writer.flush();
                final String response = IOUtils.readStreamMessage(socket.getInputStream(), IOUtils.NO_TIMEOUT);
                System.out.println("response = " + response);
                //不可以传
                if (!response.equals(IOUtils.OK)) {
                    receiveFileCallback.onError(0, response);
                    return;
                }

                //告拆服务器是下载哪个文件和请求的一些信息。
                message.addProperty(IOUtils.EXTRA_FILE_REMOTE_PATH, receiveFile.getRemoteFilePath());
                writer.write(message.toString());
                writer.append(CoolSocket.END_SEQUENCE);
                writer.flush();
                final String response2 = IOUtils.readStreamMessage(socket.getInputStream(), IOUtils.NO_TIMEOUT);
                System.out.println("response2 = " + response2);
                if (!response2.equals(IOUtils.OK)) {
                    //服务器拒绝接收
                    receiveFileCallback.onError(0, response2);
                    return;
                }
                receiveFileCallback.onStart();
                //开始接收文件
                File file = receiveFile.getLocalFile2Save();
                long size = receiveFile.getRemoteFileSize();
                FileUtils.createOrExistsFile(file);
                InputStream inputStream = socket.getInputStream();
                FileOutputStream outputStream = new FileOutputStream(file);
                int len;
                int progressPercent = -1;
                long lastNotified = System.currentTimeMillis();

                while ((len = inputStream.read(buffer)) > 0) {
                    outputStream.write(buffer, 0, len);
                    outputStream.flush();

                    if ((System.currentTimeMillis() - lastNotified) > notifyDelay) {
                        int currentPercent = (int) (((float) 100 / file.length()) * size);

                        if (currentPercent > progressPercent) {
                            receiveFileCallback.onProgress(currentPercent);
                            progressPercent = currentPercent;
                        }

                        lastNotified = System.currentTimeMillis();
                    }

                    if (isInterrupted)
                        break;
                }
                if (!isInterrupted) {
                    receiveFileCallback.onSuccess();
                }
                outputStream.close();
                inputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    socket.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                receiveFileCallback.onComplete();
            }
        }
    }

}
